"""任务执行器模块
负责实际执行任务的核心逻辑
"""
import os
import sys
import subprocess
import tempfile
import json
import traceback
from datetime import datetime

from services.shared.logger import get_logger
from services.shared.models import TaskStatus, TaskType
from services.shared.log_utils import log_error, log_info, log_system_event, log_task_operation
from services.web.app import create_app
from config.config import Config

# 获取日志记录器
logger = get_logger('executor.task_runner')

def run_task(task, params=None):
    """
    执行任务
    
    Args:
        task: 任务对象
        params: 任务参数 (可选)
        
    Returns:
        dict: 执行结果，包含输出、错误、返回码、开始时间和结束时间
    """
    # 记录开始时间
    start_time = datetime.now()
    
    logger.info(f"开始执行任务: {task.id} - {task.name}, 任务类型: {task.type}, 参数: {params}")
    logger.info(f"任务详情: {task.__dict__}")
    
    result = {
        'output': '',
        'error': '',
        'return_code': -1,
        'start_time': start_time
    }
    
    try:
        # 只支持Python脚本执行
        if not hasattr(task, 'type'):
            logger.error(f"任务对象缺少type属性: {task.__dict__}")
            result['error'] = "任务对象格式错误，缺少type属性"
            result['success'] = False
            return result
            
        logger.info(f"任务类型: {task.type}, TaskType.PYTHON_SCRIPT: {TaskType.PYTHON_SCRIPT}")
        
        if task.type == TaskType.PYTHON_SCRIPT:
            result = run_python_task(task, params)
        else:
            result['error'] = f"不支持的任务类型: {task.type}，系统只支持Python脚本执行"
            result['success'] = False
            logger.error(result['error'])
            return result
        
        # 根据返回码判断任务是否成功
        if 'success' not in result:
            result['success'] = result['return_code'] == 0
            
        logger.info(f"任务 {task.id} 执行完成，返回码: {result['return_code']}, 成功: {result['success']}")
        
        # 记录结束时间
        end_time = datetime.now()
        result['end_time'] = end_time
        
        # 判断任务是否成功
        if result.get('success', False):
            logger.info(f"任务 {task.id} 执行成功")
            
            # 记录任务执行成功的操作日志
            log_task_operation(
                action='execute',
                task_id=getattr(task, 'id', 'unknown'),
                task_name=getattr(task, 'name', 'unknown'),
                user_id=None,
                execution_time=None,
                status_code=200,
                error_message=None,
                details=f"任务执行成功: {getattr(task, 'name', 'unknown')}"
            )
        else:
            logger.error(f"任务 {task.id} 执行失败: {result.get('error', '未知错误')}")
        
        return result
        
    except Exception as e:
        error_msg = f"执行任务 {task.id} 时发生错误: {str(e)}"
        logger.error(error_msg)
        
        # 记录结束时间
        end_time = datetime.now()
        result['end_time'] = end_time
        result['success'] = False
        
        # 记录任务执行失败的操作日志
        log_task_operation(
            action='execute',
            task_id=getattr(task, 'id', 'unknown'),
            task_name=getattr(task, 'name', 'unknown'),
            user_id=None,
            execution_time=None,
            status_code=500,
            error_message=str(e),
            details=f"任务执行失败: {str(e)}"
        )
        
        result['error'] = error_msg
        result['success'] = False
        return result

# Shell任务执行功能已移除，系统只支持Python脚本执行

def run_python_task(task, params=None):
    """
    执行Python任务
    
    Args:
        task: 任务对象
        params: 任务参数 (可选)
        
    Returns:
        dict: 执行结果
    """
    # 获取脚本文件路径和工作目录
    script_file_path = None
    working_dir = None
    
    # 优先使用自定义的脚本文件路径
    if task.script_file_path:
        # 使用指定的脚本文件路径
        script_file_path = task.script_file_path
        working_dir = os.path.dirname(script_file_path)
        logger.info(f"使用自定义脚本路径: {script_file_path}")
        
        # 检查文件是否存在
        if not os.path.exists(script_file_path):
            logger.error(f"自定义脚本文件不存在: {script_file_path}")
            return {
                'output': '',
                'error': f'自定义脚本文件不存在: {script_file_path}',
                'return_code': -1,
                'success': False
            }
    elif task.user_script_id and task.user_script:
        # 从用户脚本获取文件路径
        from services.shared.file_utils import get_user_script_path
        try:
            logger.info(f"使用用户脚本路径，脚本ID: {task.user_script_id}")
            # 获取用户名
            username = task.user_script.user.username if task.user_script.user else 'admin'
            
            # 获取文件夹路径
            folder_path = None
            if task.user_script.folder:
                from services.shared.folder_utils import get_folder_full_path
                from services.shared.db_pool import get_db_session
                user_id = task.user_script.user.id if task.user_script.user else 1
                logger.info(f"开始获取文件夹路径，文件夹ID: {task.user_script.folder.id}, 名称: {task.user_script.folder.name}")
                
                # 使用当前数据库会话，避免创建新的Flask应用实例
                try:
                    db_session = get_db_session()
                    folder_rel_path, folder_abs_path = get_folder_full_path(
                        task.user_script.folder, 
                        user_id, 
                        username, 
                        db_session=db_session,
                        return_both=True
                    )
                    folder_path = folder_abs_path
                    logger.info(f"获取到文件夹相对路径: {folder_rel_path}")
                    logger.info(f"获取到文件夹绝对路径: {folder_abs_path}")
                    logger.info(f"使用文件夹路径: {folder_path}")
                except Exception as folder_error:
                    logger.error(f"获取文件夹路径失败: {str(folder_error)}")
                    # 如果获取文件夹路径失败，使用用户根目录
                    folder_path = None
            
            # 获取脚本文件的完整路径
            script_file_path = get_user_script_path(username, task.user_script.name, folder_path)
            working_dir = os.path.dirname(script_file_path)
            
            # 检查文件是否存在
            if not os.path.exists(script_file_path):
                logger.error(f"用户脚本文件不存在: {script_file_path}")
                return {
                    'output': '',
                    'error': f'用户脚本文件不存在: {script_file_path}',
                    'return_code': -1,
                    'success': False
                }
                
        except Exception as e:
            logger.error(f"获取用户脚本文件路径失败: {str(e)}")
            return {
                'output': '',
                'error': f'获取脚本文件路径失败: {str(e)}',
                'return_code': -1,
                'success': False
            }
    else:
        logger.error("任务没有指定脚本文件")
        return {
            'output': '',
            'error': '任务没有指定脚本文件',
            'return_code': -1,
            'success': False
        }
        
    # 处理任务参数和环境变量 - 通过环境变量传递
    env = os.environ.copy()
    
    # 设置PYTHONPATH以支持公共方法导入
    # 从配置文件获取公共方法目录路径
    common_methods_dir = Config.COMMON_METHODS_DIR
    
    # 如果是相对路径，需要转换为绝对路径
    if not os.path.isabs(common_methods_dir):
        # 获取项目根目录（当前工作目录）
        project_root = os.getcwd()
        common_methods_dir = os.path.join(project_root, common_methods_dir)
    
    # 将公共方法目录添加到PYTHONPATH
    current_pythonpath = env.get('PYTHONPATH', '')
    if current_pythonpath:
        env['PYTHONPATH'] = f"{common_methods_dir}:{current_pythonpath}"
    else:
        env['PYTHONPATH'] = common_methods_dir
    
    logger.info(f"设置PYTHONPATH包含公共方法目录: {common_methods_dir}")
    
    # 确保公共方法目录存在
    os.makedirs(common_methods_dir, exist_ok=True)
    
    # 处理任务的环境变量
    if hasattr(task, 'environment_variables') and task.environment_variables:
        try:
            # 解析任务的环境变量
            if isinstance(task.environment_variables, str):
                env_vars = json.loads(task.environment_variables)
            else:
                env_vars = task.environment_variables
                
            # 将任务环境变量设置到执行环境中
            for key, value in env_vars.items():
                env[key] = str(value)
                logger.info(f"设置任务环境变量: {key} = {value}")
                
        except Exception as e:
            logger.warning(f"解析任务环境变量失败: {str(e)}，将忽略环境变量")
    
    # 处理任务参数（保持向后兼容）
    if params:
        try:
            # 尝试解析JSON参数
            if isinstance(params, str):
                params_dict = json.loads(params)
            else:
                params_dict = params
                
            # 将参数设置为环境变量
            for key, value in params_dict.items():
                env[f"XBYJOB_PARAM_{key.upper()}"] = str(value)
                logger.info(f"设置参数环境变量: XBYJOB_PARAM_{key.upper()} = {value}")
                
        except Exception as e:
            logger.warning(f"解析参数失败: {str(e)}，将忽略参数")
            
            # 记录参数解析失败的系统事件日志
            log_system_event(
                level='ERROR',
                module='executor',
                message=f"任务参数解析失败: {str(e)}",
                details=json.dumps({
                    "task_id": getattr(task, 'id', 'unknown'),
                    "task_name": getattr(task, 'name', 'unknown'),
                    "params": str(params),
                    "params_type": type(params).__name__,
                    "error_type": type(e).__name__,
                    "error_message": str(e),
                    "stack_trace": traceback.format_exc(),
                    "execution_context": {
                        "function": "run_python_task",
                        "module": "executor.task_runner",
                        "operation": "params_parsing"
                    }
                }, ensure_ascii=False),
                action='PARSE_TASK_PARAMS',
                resource_type='TASK',
                resource_id=str(getattr(task, 'id', 'unknown'))
            )
    
    # 处理公共参数 - 注入用户的公共参数到环境变量
    if hasattr(task, 'user_id') and task.user_id:
        try:
            from services.shared.models import GlobalParameter
            from services.shared.db_pool import get_db_session
            from services.shared.crypto_utils import decrypt_value
            
            # 获取数据库会话
            db_session = get_db_session()
            
            # 获取用户的所有启用的公共参数
            global_params = db_session.query(GlobalParameter).filter(
                GlobalParameter.user_id == task.user_id,
                GlobalParameter.is_enabled == True
            ).all()
            
            injected_count = 0
            # 将公共参数设置为环境变量，添加GLOBAL_前缀
            for param in global_params:
                env_key = f"GLOBAL_{param.name}"
                env_value = param.value
                
                # 如果是加密参数，进行解密
                if param.is_encrypted:
                    try:
                        env_value = decrypt_value(param.value)
                        logger.info(f"成功解密公共参数: {param.name}")
                    except Exception as decrypt_error:
                        logger.error(f"解密公共参数 {param.name} 失败: {str(decrypt_error)}")
                        # 解密失败时跳过该参数
                        continue
                
                env[env_key] = str(env_value)
                injected_count += 1
                # 对于加密参数，不在日志中显示实际值
                if param.is_encrypted:
                    logger.info(f"设置加密公共参数环境变量: {env_key} = [已加密]")
                else:
                    logger.info(f"设置公共参数环境变量: {env_key} = {env_value}")
                
            logger.info(f"成功注入 {injected_count} 个公共参数到执行环境")
            
        except Exception as e:
            logger.warning(f"注入公共参数失败: {str(e)}，将继续执行任务")
            # 记录公共参数注入失败的系统事件日志
            log_system_event(
                level='WARNING',
                module='executor',
                message=f"公共参数注入失败: {str(e)}",
                details=json.dumps({
                    "task_id": getattr(task, 'id', 'unknown'),
                    "task_name": getattr(task, 'name', 'unknown'),
                    "user_id": getattr(task, 'user_id', 'unknown'),
                    "error_type": type(e).__name__,
                    "error_message": str(e),
                    "stack_trace": traceback.format_exc(),
                    "execution_context": {
                        "function": "run_python_task",
                        "module": "executor.task_runner",
                        "operation": "global_params_injection"
                    }
                }, ensure_ascii=False),
                action='INJECT_GLOBAL_PARAMS',
                resource_type='TASK',
                resource_id=str(getattr(task, 'id', 'unknown'))
            )
    
    try:
        logger.info(f"执行Python脚本: {script_file_path}")
        logger.info(f"工作目录: {working_dir}")
        
        # 获取任务超时设置
        timeout_seconds = getattr(task, 'timeout', 0)
        if timeout_seconds and timeout_seconds > 0:
            logger.info(f"任务超时设置: {timeout_seconds}秒")
        else:
            logger.info("任务无超时限制")
            timeout_seconds = None
        
        # 直接执行原始脚本文件，强制禁用Python输出缓冲
        process = subprocess.Popen(
            [sys.executable, '-u', script_file_path],  # -u 参数强制禁用输出缓冲
            stdout=subprocess.PIPE,
            stderr=subprocess.PIPE,
            text=True,
            cwd=working_dir,
            env=env,
            bufsize=0,  # 无缓冲
            universal_newlines=True
        )
        
        # 获取执行ID用于WebSocket推送
        execution_id = getattr(task, 'execution_id', None)
        
        try:
            # 实时读取输出并推送
            stdout_lines = []
            stderr_lines = []
            
            # 导入WebSocket服务
            try:
                from services.shared.websocket_service import get_websocket_service
                websocket_service = get_websocket_service()
                websocket_available = True
            except Exception as ws_import_e:
                logger.warning(f"无法导入WebSocket服务: {str(ws_import_e)}")
                websocket_available = False
            
            import threading
            import time
            import queue
            
            # 创建队列用于线程间通信
            stdout_queue = queue.Queue()
            stderr_queue = queue.Queue()
            
            def read_stdout():
                """读取stdout的线程函数"""
                try:
                    for line in iter(process.stdout.readline, ''):
                        if line:
                            stdout_queue.put(('stdout', line))
                    stdout_queue.put(('stdout', None))  # 结束标记
                except Exception as e:
                    logger.debug(f"读取stdout线程异常: {str(e)}")
                    stdout_queue.put(('stdout', None))
            
            def read_stderr():
                """读取stderr的线程函数"""
                try:
                    for line in iter(process.stderr.readline, ''):
                        if line:
                            stderr_queue.put(('stderr', line))
                    stderr_queue.put(('stderr', None))  # 结束标记
                except Exception as e:
                    logger.debug(f"读取stderr线程异常: {str(e)}")
                    stderr_queue.put(('stderr', None))
            
            # 启动读取线程
            stdout_thread = threading.Thread(target=read_stdout)
            stderr_thread = threading.Thread(target=read_stderr)
            stdout_thread.daemon = True
            stderr_thread.daemon = True
            stdout_thread.start()
            stderr_thread.start()
            
            start_time = time.time()
            stdout_finished = False
            stderr_finished = False
            
            while True:
                # 检查超时
                if timeout_seconds and (time.time() - start_time) > timeout_seconds:
                    logger.warning(f"任务执行超时({timeout_seconds}秒)，正在终止进程")
                    process.kill()
                    break
                
                # 检查进程是否结束
                process_finished = process.poll() is not None
                
                # 处理stdout队列
                try:
                    while True:
                        output_type, data = stdout_queue.get_nowait()
                        if data is None:
                            stdout_finished = True
                            break
                        stdout_lines.append(data)
                        # 实时推送stdout
                        if websocket_available and execution_id:
                            try:
                                websocket_service.emit_execution_output(
                                    execution_id=execution_id,
                                    output_type='stdout',
                                    content=data,
                                    is_realtime=True
                                )
                                logger.debug(f"实时推送stdout: {data.strip()}")
                            except Exception as ws_e:
                                logger.debug(f"WebSocket推送stdout失败: {str(ws_e)}")
                except queue.Empty:
                    pass
                
                # 处理stderr队列
                try:
                    while True:
                        output_type, data = stderr_queue.get_nowait()
                        if data is None:
                            stderr_finished = True
                            break
                        stderr_lines.append(data)
                        # 实时推送stderr
                        if websocket_available and execution_id:
                            try:
                                websocket_service.emit_execution_output(
                                    execution_id=execution_id,
                                    output_type='stderr',
                                    content=data,
                                    is_realtime=True
                                )
                                logger.debug(f"实时推送stderr: {data.strip()}")
                            except Exception as ws_e:
                                logger.debug(f"WebSocket推送stderr失败: {str(ws_e)}")
                except queue.Empty:
                    pass
                
                # 如果进程结束且所有输出都读取完毕，退出循环
                if process_finished and stdout_finished and stderr_finished:
                    break
                
                # 短暂休眠避免CPU占用过高
                time.sleep(0.05)
            
            # 等待进程结束并获取剩余输出
            try:
                remaining_stdout, remaining_stderr = process.communicate(timeout=5)
                if remaining_stdout:
                    stdout_lines.append(remaining_stdout)
                if remaining_stderr:
                    stderr_lines.append(remaining_stderr)
            except subprocess.TimeoutExpired:
                process.kill()
                remaining_stdout, remaining_stderr = process.communicate()
                if remaining_stdout:
                    stdout_lines.append(remaining_stdout)
                if remaining_stderr:
                    stderr_lines.append(remaining_stderr)
            
            return_code = process.returncode
            stdout = ''.join(stdout_lines)
            stderr = ''.join(stderr_lines)
            
            # 检查是否超时
            is_timeout = timeout_seconds and (time.time() - start_time) > timeout_seconds
            
            return {
                'output': stdout,
                'error': stderr,
                'return_code': return_code,
                'success': return_code == 0 and not is_timeout,
                'timeout': is_timeout
            }
            
        except subprocess.TimeoutExpired:
            # 超时处理
            logger.warning(f"任务执行超时({timeout_seconds}秒)，正在终止进程")
            
            # 终止进程
            process.kill()
            
            # 获取已有的输出
            try:
                stdout, stderr = process.communicate(timeout=5)  # 给5秒时间清理
            except subprocess.TimeoutExpired:
                # 强制终止
                process.kill()
                stdout, stderr = process.communicate()
            
            # 添加超时错误信息
            timeout_error = f"\n[ERROR] 任务执行超时({timeout_seconds}秒)，已被强制终止"
            stderr = (stderr or "") + timeout_error
            
            logger.error(f"任务执行超时: {timeout_seconds}秒")
            
            return {
                'output': stdout or "",
                'error': stderr,
                'return_code': -9,  # 使用-9表示被SIGKILL终止
                'success': False,
                'timeout': True  # 标记为超时
            }
        
    except Exception as e:
        logger.error(f"执行Python脚本失败: {str(e)}")
        
        # 记录脚本执行失败的任务操作日志
        log_task_operation(
            action='EXECUTE_PYTHON_SCRIPT',
            resource_type='TASK',
            resource_id=str(getattr(task, 'id', 'unknown')),
            details=f"Python脚本执行失败: {str(e)}",
            metadata={
                "task_id": getattr(task, 'id', 'unknown'),
                "task_name": getattr(task, 'name', 'unknown'),
                "script_file_path": script_file_path,
                "working_dir": working_dir,
                "error_type": type(e).__name__,
                "error_message": str(e),
                "stack_trace": traceback.format_exc(),
                "execution_context": {
                    "function": "run_python_task",
                    "module": "executor.task_runner",
                    "operation": "script_execution"
                }
            },
            level='ERROR'
        )
        
        return {
            'output': '',
            'error': f'执行脚本失败: {str(e)}',
            'return_code': -1,
            'success': False
        }